﻿using System;
using Microsoft.SPOT;

using GT = Gadgeteer;
using GTM = Gadgeteer.Modules;
using GTI = Gadgeteer.Interfaces;

namespace Gadgeteer.Modules.PeteBrown
{
    /// <summary>
    /// A NesGamepadModule module for Microsoft .NET Gadgeteer
    /// </summary>
    public class NesGamepadModule : GTM.Module
    {
        public NesGamepadModule(int socketNumber)
        {
            Socket socket = Socket.GetSocket(socketNumber, true, this, null);

            // need a Y socket.
            // Pin 6 is latch
            // Pin 7 is data A
            // Pin 8 is data B
            // Pin 9 is clock
            // Connect power to 3V3
            // Connect ground to ground

            if (!socket.SupportsType('Y'))
            {
                throw new GT.Socket.InvalidSocketException("Socket '" + socketNumber + "' does not support the Gamepad module. Please plug the module into a socket labeled 'Y'.");
            }

            // initialize the clock and latch pins
            _latch = new GTI.DigitalOutput(
                socket, 
                Socket.Pin.Six, 
                false, 
                this);

            _clock = new GTI.DigitalOutput(
                socket, 
                Socket.Pin.Nine, 
                false, 
                this);

            // initialize the data input pins
            _dataA = new GTI.DigitalInput(
                socket, 
                Socket.Pin.Seven,
                GTI.GlitchFilterMode.On, 
                GTI.ResistorMode.PullUp, 
                this);

            _dataB = new GTI.DigitalInput(
                socket, 
                Socket.Pin.Eight,
                GTI.GlitchFilterMode.On,
                GTI.ResistorMode.PullUp, 
                this);

        }

        GTI.DigitalOutput _latch;
        GTI.DigitalOutput _clock;
        GTI.DigitalInput _dataA;
        GTI.DigitalInput _dataB;

        /// <summary>
        /// The state of the buttons on the first controller
        /// </summary>
        public NesGamepadState GamepadAState;

        /// <summary>
        /// The state of the buttons on the second controller
        /// </summary>
        public NesGamepadState GamepadBState;

        /// <summary>
        /// Manually load the current button state into the Gamepad state properties
        /// </summary>
        public void UpdateState()
        {
            // bitbang to the shift register and get the results
            // if NETMF was fast, I'd put in some sleeps in the clock pulsing.

            // start by pulsing latch. 
            // This gets state for everything and shifts in button A
            _latch.Write(true);
            System.Threading.Thread.Sleep(1);
            GamepadAState.IsButtonAPressed = !_dataA.Read();
            GamepadBState.IsButtonAPressed = !_dataB.Read();
            _latch.Write(false);

            // first clock pulse gets you button B
            // Button B
            PulseClock();
            GamepadAState.IsButtonBPressed = !_dataA.Read();
            GamepadBState.IsButtonBPressed = !_dataB.Read();

            // Select
            PulseClock();
            GamepadAState.IsSelectPressed = !_dataA.Read();
            GamepadBState.IsSelectPressed = !_dataB.Read();

            // Start
            PulseClock();
            GamepadAState.IsStartPressed = !_dataA.Read();
            GamepadBState.IsStartPressed = !_dataB.Read();

            // Up
            PulseClock();
            GamepadAState.IsDPadUpPressed = !_dataA.Read();
            GamepadBState.IsDPadUpPressed = !_dataB.Read();

            // Down
            PulseClock();
            GamepadAState.IsDPadDownPressed = !_dataA.Read();
            GamepadBState.IsDPadDownPressed = !_dataB.Read();

            // Left
            PulseClock();
            GamepadAState.IsDPadLeftPressed = !_dataA.Read();
            GamepadBState.IsDPadLeftPressed = !_dataB.Read();

            // Right
            PulseClock();
            GamepadAState.IsDPadRightPressed = !_dataA.Read();
            GamepadBState.IsDPadRightPressed = !_dataB.Read();

            // finish up
            PulseClock();
        }


        private void PulseClock()
        {
            _clock.Write(true);
            System.Threading.Thread.Sleep(1);
            _clock.Write(false);
        }



        private NesGamepadState _previousAState;
        private NesGamepadState _previousBState;

        private GT.Timer _pollingTimer;

        public void EnableEvents(int pollingFrequencyMilliseconds)
        {
            _pollingTimer = new GT.Timer(pollingFrequencyMilliseconds);

            _pollingTimer.Tick += OnPollingTimerTick;

            _pollingTimer.Start();
        }

        private void OnPollingTimerTick(Timer timer)
        {
            UpdateState();

            CompareStateAndRaiseEvents(_previousAState, GamepadAState, NesGamepad.A);
            CompareStateAndRaiseEvents(_previousBState, GamepadBState, NesGamepad.B);

            _previousAState = GamepadAState;
            _previousBState = GamepadBState;
        }


        private void CompareStateAndRaiseEvents(NesGamepadState oldState, NesGamepadState newState, NesGamepad gamepad)
        {
            // TODO: Should use Program.CheckAndInvoke for raising events

            if (newState.IsButtonAPressed && !oldState.IsButtonAPressed)
            {
                if (ButtonAPressed != null)
                    ButtonAPressed(this, gamepad);
            }
            else if (!newState.IsButtonAPressed && oldState.IsButtonAPressed)
            {
                if (ButtonAReleased != null)
                    ButtonAReleased(this, gamepad);
            }


            if (newState.IsButtonBPressed && !oldState.IsButtonBPressed)
            {
                if (ButtonBPressed != null)
                    ButtonBPressed(this, gamepad);
            }
            else if (!newState.IsButtonBPressed && oldState.IsButtonBPressed)
            {
                if (ButtonBReleased != null)
                    ButtonBReleased(this, gamepad);
            }


            if (newState.IsSelectPressed && !oldState.IsSelectPressed)
            {
                if (SelectPressed != null)
                    SelectPressed(this, gamepad);
            }
            else if (!newState.IsSelectPressed && oldState.IsSelectPressed)
            {
                if (SelectReleased != null)
                    SelectReleased(this, gamepad);
            }


            if (newState.IsStartPressed && !oldState.IsStartPressed)
            {
                if (StartPressed != null)
                    StartPressed(this, gamepad);
            }
            else if (!newState.IsStartPressed && oldState.IsStartPressed)
            {
                if (StartReleased != null)
                    StartReleased(this, gamepad);
            }

            // handle direction somewhat differently as it would be used differently
            if (newState.IsDPadDownPressed != oldState.IsDPadDownPressed ||
                newState.IsDPadUpPressed != oldState.IsDPadUpPressed ||
                newState.IsDPadLeftPressed != oldState.IsDPadLeftPressed ||
                newState.IsDPadRightPressed != oldState.IsDPadRightPressed)
            {
                
                if (DirectionChanged != null)
                    DirectionChanged(this, gamepad, newState.IsDPadUpPressed, newState.IsDPadDownPressed, newState.IsDPadLeftPressed, newState.IsDPadRightPressed);
            }

        }

        public event ButtonEventHandler ButtonAPressed;
        public event ButtonEventHandler ButtonAReleased;
        public event ButtonEventHandler ButtonBPressed;
        public event ButtonEventHandler ButtonBReleased;
        public event ButtonEventHandler SelectPressed;
        public event ButtonEventHandler SelectReleased;
        public event ButtonEventHandler StartPressed;
        public event ButtonEventHandler StartReleased;
        public event DirectionEventHandler DirectionChanged;


        public void DisableEvents()
        {
            if (_pollingTimer != null)
            {
                _pollingTimer.Stop();
                _pollingTimer.Tick -= OnPollingTimerTick;
            }

            _pollingTimer = null;
        }





        /// <summary>
        /// Represents the delegate that is used to handle the <see cref="ButtonPressed"/>
        /// and <see cref="ButtonReleased"/> events.
        /// </summary>
        /// <param name="sender">The <see cref="NesGamepadModule"/> object that raised the event.</param>
        /// <param name="state">The state of the button of the <see cref="NesGamepadModule"/></param>
        public delegate void ButtonEventHandler(NesGamepadModule sender, NesGamepad gamepad);
        public delegate void DirectionEventHandler(NesGamepadModule sender, NesGamepad gamepad, bool up, bool down, bool left, bool right);



    }
}
